
package com.gcloud.controller.storage.service.impl;

import com.gcloud.common.util.StringUtils;
import com.gcloud.controller.ResourceProviders;
import com.gcloud.controller.compute.dao.ComputeNodeDao;
import com.gcloud.controller.compute.entity.ComputeNode;
import com.gcloud.controller.storage.dao.DiskCategoryDao;
import com.gcloud.controller.storage.dao.StoragePoolDao;
import com.gcloud.controller.storage.entity.DiskCategory;
import com.gcloud.controller.storage.entity.StoragePool;
import com.gcloud.controller.storage.provider.IStoragePoolProvider;
import com.gcloud.controller.storage.service.IStoragePoolService;
import com.gcloud.core.exception.GCloudException;
import com.gcloud.framework.db.PageResult;
import com.gcloud.header.compute.enums.StorageType;
import com.gcloud.header.enums.ProviderType;
import com.gcloud.header.enums.ResourceType;
import com.gcloud.header.storage.StorageErrorCodes;
import com.gcloud.header.storage.model.DiskCategoryModel;
import com.gcloud.header.storage.model.StoragePoolModel;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Transactional(propagation = Propagation.REQUIRED)
@Component
public class StoragePoolServiceImpl implements IStoragePoolService {

    @Autowired
    private StoragePoolDao poolDao;

    @Autowired
    private DiskCategoryDao categoryDao;

    @Autowired
    private ComputeNodeDao nodeDao;

    @Override
    public List<DiskCategoryModel> describeDiskCategories(String zoneId) {
        Map<String, Object> props = new HashMap<>();
        if (zoneId != null) {
            props.put("zoneId", zoneId);
        }
        return this.categoryDao.findByProperties(props, DiskCategoryModel.class);
    }

    @Override
    public String createDiskCategory(String zoneId, String code, String name, Integer minSize, Integer maxSize) {
        synchronized (StoragePoolServiceImpl.class) {
            for (DiskCategory category : this.categoryDao.findByProperty("zoneId", zoneId)) {
                if (category.getCode().equals(code) || category.getName().equals(name)) {
                    throw new GCloudException(StorageErrorCodes.CATEGORY_ALREADY_EXISTS);
                }
            }
            DiskCategory entity = new DiskCategory();
            entity.setId(StringUtils.genUuid());
            entity.setZoneId(zoneId);
            entity.setCode(code);
            entity.setName(name);
            entity.setMinSize(minSize);
            entity.setMaxSize(maxSize);
            this.categoryDao.save(entity);
            return entity.getId();
        }
    }

    @Override
    public PageResult<StoragePoolModel> describeStoragePools(int pageNumber, int pageSize) {
        return this.poolDao.describeStoragePools(pageNumber, pageSize, StoragePoolModel.class);
    }

    private DiskCategory checkAndGetCategory(String categoryId) throws GCloudException {
        if (categoryId != null) {
            DiskCategory res = this.categoryDao.getById(categoryId);
            if (res != null) {
                return res;
            }
        }
        throw new GCloudException(StorageErrorCodes.NO_SUCH_STORAGE_TYPE);
    }

    private DiskCategory checkAndGetCategory(String zoneId, String code) throws GCloudException {
        Map<String, Object> props = new HashMap<>();
        props.put("zoneId", zoneId);
        props.put("code", code);
        List<DiskCategory> res = this.categoryDao.findByProperties(props);
        if (res.isEmpty()) {
            throw new GCloudException(StorageErrorCodes.NO_SUCH_STORAGE_TYPE);
        }
        return res.get(0);
    }

    @Override
    public String createStoragePool(String displayName, Integer providerType, String storageType, String poolName, String zoneId, String categoryId, String hostname, String taskId)
            throws GCloudException {
        if (StorageType.value(storageType) == null) {
            throw new GCloudException(StorageErrorCodes.NO_SUCH_STORAGE_TYPE);
        }
        this.checkAndGetCategory(categoryId);
        StoragePool pool = new StoragePool();
        pool.setId(StringUtils.genUuid());
        synchronized (StringUtils.syncStringObject("" + providerType + storageType + poolName)) {
            if (this.poolDao.find(providerType, storageType, poolName) != null) {
                throw new GCloudException(StorageErrorCodes.POOL_ALREADY_EXISTS);
            }
            String refId = this.getProvider(providerType).createStoragePool(pool.getId(), storageType, poolName, hostname, taskId);
            {
                pool.setDisplayName(displayName);
                pool.setStorageType(storageType);
                pool.setPoolName(poolName);
                pool.setProvider(providerType);
                pool.setProviderRefId(refId);
                pool.setZoneId(zoneId);
                pool.setCategoryId(categoryId);
                pool.setHostname(hostname);
                this.poolDao.save(pool);
            }
        }
        return pool.getId();
    }

    @Override
    public String reportStoragePool(String displayName, Integer providerType, String storageType, String poolName, String categoryCode, String hostname) throws GCloudException {

        if (ProviderType.get(providerType) == null) {
            throw new GCloudException("provider type " + providerType + " not found");
        }

        if (StringUtils.isBlank(storageType) || StorageType.value(storageType) == null) {
            throw new GCloudException(StorageErrorCodes.NO_SUCH_STORAGE_TYPE);
        }

        if (StringUtils.isBlank(poolName)) {
            throw new GCloudException(StorageErrorCodes.INPUT_POOL_NAME_ERROR);
        }

        if (StringUtils.isBlank(displayName)) {
            displayName = hostname + "_local";
        }

        ComputeNode node = null;
        if (StringUtils.isNotBlank(hostname)) {
            node = nodeDao.findUniqueByProperty(ComputeNode.HOSTNAME, hostname);
        }
        if (node == null) {
            throw new GCloudException("node " + hostname + " not found");
        }

        DiskCategory category = this.checkAndGetCategory(node.getZoneId(), categoryCode);

        StoragePool pool = new StoragePool();
        pool.setId(StringUtils.genUuid());
        synchronized (StringUtils.syncStringObject("" + providerType + storageType + poolName)) {
            if (this.poolDao.find(providerType, storageType, poolName) != null) {
                throw new GCloudException(StorageErrorCodes.POOL_ALREADY_EXISTS);
            }
            {
                pool.setDisplayName(displayName);
                pool.setStorageType(storageType);
                pool.setPoolName(poolName);
                pool.setProvider(providerType);
                pool.setProviderRefId(pool.getId());
                pool.setZoneId(node.getZoneId());
                pool.setCategoryId(category.getId());
                pool.setHostname(hostname);
                this.poolDao.save(pool);
            }
        }
        return pool.getId();
    }

    @Override
    public void modifyStoragePool(String poolId, String displayName) throws GCloudException {
        synchronized (StringUtils.syncStringObject(poolId)) {
            StoragePool pool = this.poolDao.checkAndGet(poolId);
            if (!StringUtils.equals(pool.getDisplayName(), displayName)) {
                List<String> updateFields = new ArrayList<>();
                updateFields.add(pool.updateDisplayName(displayName));
                this.poolDao.update(pool, updateFields);
            }
        }
    }

    @Override
    public void deleteStoragePool(String poolId) throws GCloudException {
        synchronized (StringUtils.syncStringObject(poolId)) {
            StoragePool pool = this.poolDao.checkAndGet(poolId);
            this.getProvider(pool.getProvider()).deleteStoragePool(pool.getStorageType(), pool.getProviderRefId(), pool.getPoolName());
            this.poolDao.deleteById(poolId);
        }
    }

    private IStoragePoolProvider getProvider(int providerType) {
        IStoragePoolProvider provider = ResourceProviders.checkAndGet(ResourceType.STORAGE_POOL, providerType);
        return provider;
    }

}
